﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine.Sgml.Schema;
using System.IO;

namespace Engine.Sgml.Test.Util {
    public class SgmlDocumentGenerator {
        private Random random;
        private SgmlDocumentConfiguration configuration;

        public SgmlDocumentGenerator(bool generateComments) {
            random = new Random();
            configuration = new SgmlDocumentConfiguration();
            configuration.GenerateComments = generateComments;
            }

        public SgmlDocumentGenerator() : this(false) { }

        public SgmlDocument Generate(DElementDeclaration declaration) {
            SgmlDocument document = new SgmlDocument();
            SElement element = BuildElement(declaration, document.Root);
            DModelGroups model = declaration.ContentModel.Groups;
            BuildElements(element, GetGroupItems(model));
            if (configuration.DiffuseRepetitiveElements)
                DiffuseRepetitiveElements(element, model);
            return document;
            }

        private IEnumerable<IModelGroupsItem> GetGroupItems(DModelGroups model) {
            List<IModelGroupsItem> groupItems = new List<IModelGroupsItem>();
            switch (model.OrderType) {
                case DGroupOrderType.OnlyOne:
                    groupItems.Add(model[random.Next(model.Count)]);
                    break;
                case DGroupOrderType.Sequence:
                case DGroupOrderType.AnyOrder:
                    int itemsCount = random.Next(1, model.Count);
                    do {
                        int nextItem = random.Next(model.Count);
                        while (groupItems.Exists(i => i.OrderIndex == nextItem)) {
                            nextItem = random.Next(model.Count);
                            }
                        groupItems.Add(model[nextItem]);
                        } while (groupItems.Count != itemsCount);
                    break;
                default:
                    throw new InvalidOperationException();
                }
            return groupItems;
            }

        private void BuildElements(SElement ancestor, IEnumerable<IModelGroupsItem> groupItems) {
            IEnumerable<DName> inclusions = ancestor.Declaration.ContentModel.Inclusions;
            foreach (IModelGroupsItem item in groupItems) {
                if (item.IsModelGroups()) {
                    DModelGroups model = item.FirstItem.Parent;
                    BuildElements(ancestor, GetGroupItems(model));
                    if (model.CanRecur() && configuration.RepeatGroupItem)
                        RepeatModelGroups(ancestor, model);
                    }
                else {
                    SElement e = BuildElement(item, ancestor);
                    BuildChildNodes(e);
                    BuildComment(ancestor);
                    if (item.CanRecur() && configuration.RepeatGroupItem)
                        RepeatElement(ancestor, item);
                    }
                if (configuration.AddIncluisonElement && inclusions.Count() > 0) {
                    int count = random.Next(1, inclusions.Count());
                    for (int i = 0; i < count; i++) {
                        SElement e = BuildElement(inclusions.ElementAt(i), ancestor);
                        BuildChildNodes(e);
                        BuildComment(ancestor);
                        }
                    }
                }
            }

        private void RepeatModelGroups(SElement ancestor, DModelGroups model) {
            int repeatCount = random.Next(1, 4);
            do {
                IEnumerable<IModelGroupsItem> groupItems = GetGroupItems(model);
                foreach (IModelGroupsItem item in groupItems) {
                    if (item.IsModelGroups())
                        BuildElements(ancestor, GetGroupItems(item.FirstItem.Parent));
                    else {
                        SElement e = BuildElement(item, ancestor);
                        BuildChildNodes(e);
                        BuildComment(ancestor);
                        }
                    }
                repeatCount--;
                } while (repeatCount > 0);
            }

        private void RepeatElement(SElement ancestor, IModelGroupsItem item) {
            int repeatCount = random.Next(1, 4);
            do {
                SElement e = BuildElement(item, ancestor);
                BuildChildNodes(e);
                repeatCount--;
                } while (repeatCount > 0);
            }

        private static SElement BuildElement(IModelGroupsItem item, SElement ancestor) {
            if (item.Declaration == null)
                return BuildElement(item.Name, ancestor);
            else
                return BuildElement(item.Declaration, ancestor);
            }

        private static SElement BuildElement(DName name, SElement ancestor) {
            SElement element = ancestor.Nodes.Push();
            element.Clear();
            element.Name = name.ToString();
            element.DName = name;
            element.Parent = ancestor;
            if (name == DName.PCData)
                element.NodeType = SgmlNodeType.Text;
            else if (name == DName.CData)
                element.NodeType = SgmlNodeType.CData;
            else
                element.NodeType = SgmlNodeType.Element;
            return element;
            }

        private static SElement BuildElement(DElementDeclaration declaration, SElement ancestor) {
            SElement element = ancestor.Nodes.Push();
            element.Clear();
            element.Name = declaration.Name.ToString();
            element.NodeType = SgmlNodeType.Element;
            element.DName = declaration.Name;
            element.Declaration = declaration;
            element.Parent = ancestor;
            return element;
            }

        private void BuildComment(SElement ancestor) {
            if (configuration.GenerateComments && random.Next(0, 2) == 1) {
                SElement comment = ancestor.Nodes.Push();
                comment.Clear();
                comment.Name = DName.PCData.ToString();
                comment.NodeType = SgmlNodeType.Comment;
                comment.DName = DName.PCData;
                comment.Declaration = null;
                comment.Parent = ancestor;
                comment.Value = "COMMENT";
                }
            }

        private void BuildChildNodes(SElement element) {
            if (configuration.MaxDepth >= element.Depth())
                return;
            if (element.Declaration == null || element.Declaration.ContentModel.ContentType == DContentModelType.Empty)
                return;
            BuildElements(element, GetGroupItems(element.Declaration.ContentModel.Groups));
            }

        private void DiffuseRepetitiveElements(SElement ancestor, DModelGroups model) {
            for (int i = 0; i < ancestor.Nodes.Count; i++) {
                SElement element = ancestor.Nodes[i];
                IModelGroupsItem groupItem = model.FirstOrDefault(m => m.Name == element.DName);
                if (groupItem == null)
                    continue;
                if (groupItem.CanRecur() && configuration.RepeatGroupItem) {
                    int position = 0;
                    if (configuration.DiffuseAfterElement) {
                        position = random.Next(element.OrderIndex + 1, ancestor.Nodes.Count);
                        if (i == position)
                            i++;
                        }
                    else
                        position = random.Next(element.OrderIndex);

                    SElement e = BuildElement(groupItem, ancestor);
                    BuildChildNodes(e);
                    ancestor.Nodes.Move(e.OrderIndex, position);
                    }
                }
            }
        }
    }
